﻿/*
* Copyright 2004-2010 mb_sx@hotmail.com
* All Rights Reserved.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Data;
using System.Windows.Forms;
using System.Threading;
using System.ComponentModel;
using DeskNotes.Utils;

namespace DeskNotes
{
    class SearchableArchive : SearchableArchiveDB
    {
        
        internal static char[] DelimiterChars = { ' ', ',', '.', ':', '\t', '+', '-', '"' };

        internal const string DEFAULT_SEARCHABLE_ARCHIVE = "DeskNotes_ArchIndex";
        internal const string DEFAULT_DESKNOTES_FOLDER = "MB\\DeskNotes";

        private System.ComponentModel.BackgroundWorker background;

        internal SearchableArchive() : base(){

        }

        private string filename;
        private bool indexFilled = false;

        public bool IsFilled
        {
            get { return indexFilled; }
        }
        
        public DateTime LastUpdate
        {
            get {
                DateTime dt = File.GetLastWriteTime(filename);
                return dt;
            }
            
        }

        public string Filename
        {
            get { return filename; }
            set {
                if (!File.Exists(value))
                {
                    if (!Directory.Exists(value))
                    {
                        FileInfo fi = new FileInfo(value);
                        Directory.CreateDirectory(fi.DirectoryName);
                        fi = null;
                    }
                    FileStream f = File.Create(value);
                    f.Close();
                    indexFilled = false;
                }
                filename = value;
            }
        }

        /// <summary>
        /// Opens and reads the content of SearchableArchive from disk.
        /// </summary>
        /// <returns>true when content was read</returns>
        internal bool Open()
        {
            try
            {
                this.ReadXml(filename);
                indexFilled = true;
                return true;

            }
            catch (FileNotFoundException fnfe)
            {
                Logger.WriteLine(fnfe.Message + "\\n" + fnfe.StackTrace);
                return false;
            }
            catch (XmlException ex)
            {
                Logger.WriteLine(ex.Message + "\\n" + ex.StackTrace);
                return false;
            }     
        }

        /// <summary>
        /// Saves the SearchableArchive - Index to disk
        /// </summary>
        internal void Save()
        {
            this.WriteXml(this.filename, XmlWriteMode.WriteSchema);
        }

        /// <summary>
        /// When new searchable archive index is needed then existing file
        /// is deleted, recreated and read.
        /// </summary>
        internal void DeleteContent()
        {
            this.Notes.Clear();
            this.Notes.AcceptChanges();
            //SearchableArchiveDB db = new SearchableArchiveDB();
            //db.WriteXmlSchema(filename);
            //db = null;
            indexFilled = false;
        }

        internal void FillData(string archive, System.ComponentModel.ProgressChangedEventHandler changeHandler, System.ComponentModel.RunWorkerCompletedEventHandler completedHandler)
        {
            background = new BackgroundWorker();
            background.WorkerReportsProgress = true;
            background.DoWork += new System.ComponentModel.DoWorkEventHandler(background_DoWork);
            background.RunWorkerCompleted += completedHandler;
            background.ProgressChanged += changeHandler;
            background.RunWorkerAsync(archive);
            
            
        }

        
        /// <summary>
        /// Performs background creation of Archive Index.
        /// It uses own instance of RichTextBox class due to hangs of DeskNotes when RTFbox was not disposed, 
        /// when created in RtfConverter class.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void background_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            BackgroundWorker thread = sender as BackgroundWorker;
            string archName = (string)e.Argument;
            
            ArchivedNotes archive = new ArchivedNotes();
            archive.ReadXml(archName);
            ArchivedNotes.ArchivedNoteDataTable table = archive.ArchivedNote;
            ArchivedNotes.ArchivedNoteRow[] notes = (ArchivedNotes.ArchivedNoteRow[])table.Select();
           
            int maxRec = notes.Length;
            int count = 1;
            foreach (ArchivedNotes.ArchivedNoteRow row in notes)
            {
                if (thread.CancellationPending)
                {
                    e.Cancel = true;
                    archive.Dispose();
                    archive = null;
                    return;
                }

                this.Notes.AddNotesRow(RtfConverter.TextForBackground(row.text), row.ID);
                // Handle BackgroundWorker progress
                thread.ReportProgress((int)(((float)count / (float)maxRec)*100));
                count++;
            }
            indexFilled = true;
            this.AcceptChanges();
            this.WriteXml(filename, XmlWriteMode.WriteSchema);
            archive.Dispose();
            archive = null;
        }

        

        /// <summary>
        /// Update existing archived note in Index and save SearchableArchive
        /// </summary>
        /// <param name="rtfText">RTF text of updated archive note, is coverted to plain text</param>
        /// <param name="id">ID of update archive note</param>
        internal void Update(string rtfText, long id)
        {
            SearchableArchiveDB.NotesRow[] notes = (SearchableArchiveDB.NotesRow[])this.Notes.Select("ID=" + id.ToString());
            if (0 < notes.Length)
            {
                notes[0].text = RtfConverter.Text(rtfText);
            }
            this.AcceptChanges();
        }

        /// <summary>
        /// Adds Note into SearchableArchive - Index
        /// </summary>
        /// <param name="rtfText">RTF text to be added, is converted to plain text</param>
        /// <param name="id">ID of note in Archive</param>
        internal void Add(string rtfText, long id)
        {
            this.Notes.AddNotesRow(RtfConverter.Text(rtfText), id);
            this.AcceptChanges();
        }

        /// <summary>
        /// Removes Note from SearchableArchive - Index by its ID
        /// </summary>
        /// <param name="id">ID of Note to be removed</param>
        internal void Remove(long id)
        {
            SearchableArchiveDB.NotesRow[] notes = (SearchableArchiveDB.NotesRow[])this.Notes.Select("ID=" + id.ToString());
            if (0 < notes.Length)
            {
                this.Notes.RemoveNotesRow(notes[0]);
                this.AcceptChanges();
            }
        }


        internal SearchableArchiveDB.NotesRow[] Search(string s, ref List<SearchedText> markableText)
        {
            string select = "";
            string[] search;
        
            if ((s.StartsWith("\"")) && (s.EndsWith("\""))) //Exact string including space or '+' or '-'
            {
                s = s.Substring(1, s.LastIndexOf('"') - 1);
                markableText.Add(new SearchedText(s));
                select = "text LIKE '*" + s + "*'";
            }
            else if (s.Contains("+")) //one term AND another term
            {
                search = s.Split(SearchableArchive.DelimiterChars, StringSplitOptions.RemoveEmptyEntries);
                markableText.Add(new SearchedText(search[0]));
                markableText.Add(new SearchedText(search[1]));
                select = "(text LIKE '*" + search[0] + "*') AND (text LIKE '*" + search[1] + "*')";
            }
            else if (s.Contains("-"))
            { //one term AND NOT 2nd term
                search = s.Split(SearchableArchive.DelimiterChars, StringSplitOptions.RemoveEmptyEntries);
                markableText.Add(new SearchedText(search[0]));
                select = "(text LIKE '*" + search[0] + "*') AND (text NOT LIKE '*" + search[1] + "*')";
            }
            else if (s.Contains(" ")) // term 1 OR term 2
            {
                search = s.Split(SearchableArchive.DelimiterChars, StringSplitOptions.RemoveEmptyEntries);
                markableText.Add(new SearchedText(search[0]));
                markableText.Add(new SearchedText(search[1]));
                select = "(text LIKE '*" + search[0] + "*') OR (text LIKE '*" + search[1] + "*')";
            }
            else  //default search the string as it is, should be just one term
            {
                markableText.Add(new SearchedText(s));
                select = "text LIKE '*" + s + "*'";
            }

            SearchableArchiveDB.NotesRow[] searchResult = (SearchableArchiveDB.NotesRow[])this.Notes.Select(select, "text ASC");
            return searchResult;
        }

        
        
    }
}
